package org.hagopa.games.moneypool.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.hagopa.games.moneypool.Configuration;
import org.hagopa.games.moneypool.ServerVersion;
import org.hagopa.games.moneypool.dblayer.Connector.DBConnector;
import org.hagopa.games.moneypool.msgs.CommandsEnum;
import org.hagopa.games.moneypool.msgs.Message;
import org.hagopa.games.moneypool.msgs.ContentTypes.service.Version;

public class Server implements Runnable {
	Selector sel = null;
	private ServerSocket srvSocket = null;
	private int port = 3456;
	private static ServerSocketChannel ssc = null;
	private Reception waitReception = null;
	private static int clid = 1;
	private List requestQ = null;
	
	private boolean isRunning = false;
	
	public Server(int port)
	{
		this.port = port;
		
		//new Thread(waitReception).start();
		
	}
	public void setRequestsQ(List<Long> outQ)
	{
		requestQ = outQ;
	}
	public boolean init(Reception rec)
	{
		this.waitReception = rec;
		this.isRunning = true;
		
		DBConnector nDB = DBConnector.getInstance();
		DBConnector.setInitialSettings(Configuration.getDBHost(),Configuration.getUser(), 
											Configuration.getPwd(), Configuration.getDbname());
		if(null == nDB)
		{
			System.out.println("Failed to create DB handler!");
			return false;
		}
		DBConnector.connect();
		if(DBConnector.isConnected()==false)
			return false;
		new Thread( this ).start();
			return true;
	}
	private long createSessionId()
	{
		int sessId = 12;

		Date dt = new Date();
		return (sessId * dt.getTime()); 
	}
	
	private synchronized  SocketChannel accept(SelectionKey key) throws IOException {
		// For an accept to be pending the channel must be a server socket channel.
		ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
		System.out.println("\n=================\nAccept New Connection!!\n=================");
		
		Message reply = new Message();
		reply.setClientId(0);
		
		clid++;
		
		reply.setSessionId(createSessionId());
		reply.setCommand(CommandsEnum.SERV_HELLO);
		reply.setContentSz(8);
		
		Version vers = new Version();
		vers.setMajor(ServerVersion.getMain());
		vers.setSubVer(ServerVersion.getMajor());
		
		reply.setContentHandler(vers);
		
		// Accept the connection and make it non-blocking
		SocketChannel socketChannel = serverSocketChannel.accept();
		//Socket socket = socketChannel.socket();
		socketChannel.configureBlocking(false);
		InetAddress ipa = socketChannel.socket().getInetAddress();
		System.out.println("IPAddress : " + ipa.getHostName());
		byte[] msg = null;
		msg = reply.marshall();
	
		ByteBuffer src = ByteBuffer.wrap(msg);
		
		socketChannel.write(src);
		System.out.println(" [Accept function] Sent Message");
		
		socketChannel.register(this.sel, SelectionKey.OP_WRITE);
		socketChannel.register(this.sel, SelectionKey.OP_READ);
		
		return socketChannel;
	}
	
	private boolean startServer()
	{
		System.out.println("\n=================\nStarting Server!!!!!\n=================\n");
		ServerVersion.Print();
		try {
			// Instead of creating a ServerSocket,
		      // create a ServerSocketChannel
			ssc = ServerSocketChannel.open();

		      // Set it to non-blocking, so we can use select
			ssc.configureBlocking( false );

		      // Get the Socket connected to this channel, and bind it
		      // to the listening port
		    srvSocket = ssc.socket();
		      
		    if(null == srvSocket)
			{
				System.out.println("Failed to create Server Socket");
				return false;
			}
		      
		    InetSocketAddress isa = new InetSocketAddress( port );
		    srvSocket.bind( isa );

			sel = Selector.open();
		
			ssc.register(sel, SelectionKey.OP_ACCEPT);
		}
		catch (ClosedChannelException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} 
		
		
		return true;
	}
	
	
	public void run() 
	{
		
		if(startServer() == true)
		{
			System.out.println("\n-----------------\nStarted succesfully\n--------------------\n");
			while(isRunning)
			{
				int num = 0;
				try {
					num = sel.select();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if(num == 0)
					continue;
				
				Set<SelectionKey> keys = Collections.synchronizedSet(sel.selectedKeys());
				
				Iterator<SelectionKey> it = keys.iterator();
					while(it.hasNext())
					{
				
					SelectionKey skey = (SelectionKey)it.next();
					it.remove();
						if(skey.isValid() && skey.isAcceptable())
						{
							SocketChannel cs = null;
							try {
								cs = accept(skey);
								NetClientHandler client = new NetClientHandler(cs);
								if(null == client)
								{
									System.out.println("No client created!!!");
									break;
								}
								else
								{
									synchronized (waitReception) {
										waitReception.appendNewClient(client);
									}
								}
								
								System.out.println("-----Now in Reception: " + waitReception.getWaitingClientsNo() + " clients in Reception");

							} catch (IOException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							
						}
						else if (skey.isValid() && skey.isReadable())
						{
							System.out.println("\n-------\nReadable!!!");
							
							isReadableEvent(skey);
							
						}
					}
				keys.clear();
				cleanUpReception();
			}
		
			
		}
	
	}
	
	public void cleanUpReception()
	{
		Iterator <NetClientHandler> it = this.waitReception.getIter();
		while(it.hasNext())
		{
			NetClientHandler tmp = it.next();
			if(tmp.getChannel().isConnected() == false)
				it.remove();
		}
	}
	
	public void isReadableEvent(SelectionKey skey)
	{
		Iterator<NetClientHandler> iter =  waitReception.getIter();
		SelectableChannel channel = skey.channel();
		while(iter.hasNext())
		{
			NetClientHandler tmp = (NetClientHandler)iter.next();
			if(channel.equals(tmp.getChannel()))
			{
				System.out.println("Found Channel");
				if(tmp.getStatus().compareTo(ClientStatus.PLAYING) != 0)
				{
					if(tmp.getStatus().compareTo(ClientStatus.AFTER_HANDSHAKE) == 0)
					{
						System.out.println("Found Client After Handshake");
						if(null != tmp )
						{
							System.out.println("ClientId: " + tmp.getClientId());
							if(tmp.getMsg()!= null)
							{
								synchronized (requestQ) {
									requestQ.add(new Long(tmp.getClientId()));
									System.out.println("Add ClientId to Q");	
								}
								
								skey.cancel();
							}
							else
							{
								removeChannel(skey);
							}
						
						}
						else
						{
							System.out.println("No ClientHandler!!!");
						}
						
					}
					else if(tmp.getChannel().isConnected() == true)
					{
						if(null == tmp.getMsg())
						{
							removeChannel(skey);
							break;
						}
						tmp.response();		
					}
				}
			}
		}
	}
	
	private void removeChannel(SelectionKey key)
	{
		
		key.cancel();
		try {
			key.channel().close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void stopServer()
	{
		this.isRunning = false;
	}
}
